﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.IO;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System.IO.Compression;

namespace JsonRpc
{

    public partial class JsonRpcService : IHttpHandler
    {

        public long StartCompressionAt { get; set; }


        public JsonRpcService()
        {
            StartCompressionAt = -1;
        }

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            string jsonResponse = null;
            try
            {
                bool hasExtraSegments = !context.Request.Url.Segments[context.Request.Url.Segments.Length - 1].Contains(".ashx");
                TextReader reader = new StreamReader(context.Request.InputStream);
                string json = reader.ReadToEnd().Trim();
                RequestingEventArgs reqArgs = new RequestingEventArgs { RequestText = json };
                OnRequesting(reqArgs);
                json = reqArgs.RequestText;

                if (json.Length == 0)
                {

                    //write the request from the Url Segments
                    if (!hasExtraSegments)
                    {
                        string js = GenerateScript();

                        //context.Response.ContentType = "text/javascript";
                        context.Response.ContentType = "application/x-javascript";
                        context.Response.Write(js);
                        context.Response.End();
                        return;
                    }

                    return;
                }


                
                if (json != string.Empty)
                {
                    if (json[0] == '[')
                    {
                        List<JsonRpcResponse> batch = new List<JsonRpcResponse>();
                        JArray jArray = JArray.Parse(json);
                        foreach (JObject jObj in jArray)
                        {
                            JsonRpcResponse response = Process(jObj);
                            batch.Add(response);
                        }

                        jsonResponse = JsonConvert.SerializeObject(batch);
                    }
                    else
                    {
                        JObject jFirstObj = JObject.Parse(json);
                        JsonRpcResponse response = Process(jFirstObj);
                        jsonResponse = JsonConvert.SerializeObject(response);
                    }
                }


                


            }
            catch (Exception ex)
            {
                //NxResponse response = new NxResponse(ex.InnerException);

                JsonRpcResponse response = new JsonRpcResponse(ex);
                
                jsonResponse = JsonConvert.SerializeObject(response);
            }


            if (StartCompressionAt >= 0)
            {
                if (jsonResponse.Length * 2 > StartCompressionAt)
                {
                    string acceptEncoding = context.Request.Headers["Accept-Encoding"];

                    if (acceptEncoding != null)
                    {
                        if (acceptEncoding.Contains("gzip"))
                        {
                            context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
                            context.Response.AppendHeader("Content-Encoding", "gzip");
                        }
                        else if (acceptEncoding.Contains("deflate"))
                        {
                            context.Response.Filter = new DeflateStream(context.Response.Filter, CompressionMode.Compress);
                            context.Response.AppendHeader("Content-Encoding", "deflate");
                        }
                    } 
                }
            }

            RespondingEventArgs resArgs = new RespondingEventArgs { ResponseText = jsonResponse };
            OnResponding(resArgs);
            jsonResponse = resArgs.ResponseText;

            context.Response.Write(jsonResponse);
            context.Response.End();
        }

        private JsonRpcResponse Process(JObject jObj)
        {
            JsonRpcResponse response = new JsonRpcResponse();
            try
            {
                string rpcVersion = null;
                string rpcMethod = null;
                string rpcId = null;

                List<object> realParams = new List<object>();


                JEnumerable<JProperty> jProperties = jObj.Children<JProperty>();

                JProperty versionProperty = jProperties.FirstOrDefault(p => p.Name == "jsonrpc");
                if (versionProperty != null)
                    rpcVersion = JsonConvert.DeserializeObject(versionProperty.Value.ToString(), typeof(string)).ToString();


                JProperty methodProperty = jProperties.FirstOrDefault(p => p.Name == "method");
                if (methodProperty != null)
                {
                    string s = (string)JsonConvert.DeserializeObject(methodProperty.Value.ToString(), typeof(string));
                    if (s != null)
                        rpcMethod = s;
                }
                else
                {
                    //throw an error
                }

                JProperty idProperty = jProperties.FirstOrDefault(p => p.Name == "id");
                if (idProperty != null)
                    rpcId = (string)JsonConvert.DeserializeObject(idProperty.Value.ToString(), typeof(string));





                MethodInfo requestedMethodInfo = null;
                MethodInfo requestedUnnamedMethodInfo = null;

                MethodInfo[] methods = this.GetType().GetMethods();
                foreach (MethodInfo m in methods)
                {
                    JsonRpcMethod[] mInfos = (JsonRpcMethod[])m.GetCustomAttributes(typeof(JsonRpcMethod), false);


                    if (mInfos.Length > 0)
                    {
                        var minfo = mInfos.FirstOrDefault(f => f.Name == rpcMethod);
                        if (minfo != null)
                        {
                            requestedMethodInfo = m;
                            break;
                        }
                        else
                        {
                            if (m.Name == rpcMethod)
                            {
                                requestedUnnamedMethodInfo = m;
                            }
                        }
                    }

                }

                if (requestedMethodInfo == null && requestedUnnamedMethodInfo != null)
                    requestedMethodInfo = requestedUnnamedMethodInfo;

                if (requestedMethodInfo == null)
                {
                    response.error = new JsonRpcError();
                    response.error.message = "Server method not found! [" + rpcMethod + "]";
                    return response;
                }


                ParameterInfo[] paramaters = requestedMethodInfo.GetParameters();
                JProperty jParams = jProperties.FirstOrDefault(j => j.Name == "params");

                if (jParams != null)
                {
                    string sParams = jParams.Value.ToString().Trim();
                    if (sParams[0] == '[')
                    {
                        JArray jArrayParams = JArray.Parse(sParams);
                        int kParams = 0;
                        foreach (ParameterInfo pi in paramaters)
                        {
                            object p = JsonConvert.DeserializeObject(jArrayParams[kParams].ToString(), pi.ParameterType);
                            realParams.Add(p);
                            kParams++;
                        }
                    }
                    else
                    {
                        JObject jParamsObject = JObject.Parse(sParams);
                        JEnumerable<JProperty> jParamsProperties = jParamsObject.Children<JProperty>();
                        foreach (ParameterInfo pi in paramaters)
                        {
                            JProperty jParam = jParamsProperties.FirstOrDefault(j => j.Name == pi.Name);
                            object p = JsonConvert.DeserializeObject(jParam.Value.ToString(), pi.ParameterType);
                            realParams.Add(p);
                        }
                    }
                }

                JsonRpcRequest reqObj = new JsonRpcRequest
                {
                    jsonrpc = rpcVersion,
                    id = rpcId,
                    method = rpcMethod,
                    parameters = realParams
                };

                object res;
                JsonRpcResponse rpcResponse = new JsonRpcResponse{id = rpcId};
                try
                {
                    MethodInvokingEventArgs mInvokingArgs = new MethodInvokingEventArgs { request = reqObj };
                    OnMethodInvoking(mInvokingArgs);

                    res = requestedMethodInfo.Invoke(this, mInvokingArgs.request.parameters.ToArray());
                    rpcResponse.result = res;
                    
                    MethodInvokedEventArgs mInvokedArgs = new MethodInvokedEventArgs { Response = rpcResponse };
                    OnMethodInvoked(mInvokedArgs);

                    return mInvokedArgs.Response;
                }
                catch (Exception ex)
                {
                    string errMsg = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
                    rpcResponse.error = new JsonRpcError((int)JsonRpcErrorCodes.ExecuteError, errMsg);

                    MethodInvokedEventArgs mInvokedArgs = new MethodInvokedEventArgs { Response = rpcResponse };
                    OnMethodInvoked(mInvokedArgs);

                    return mInvokedArgs.Response;

                }

            }
            catch (Exception ex)
            {
                JsonRpcResponse rpcResponse = new JsonRpcResponse { id = "0" };
                string errMsg = ex.InnerException != null ? ex.InnerException.Message : ex.Message;
                rpcResponse.error = new JsonRpcError((int)JsonRpcErrorCodes.Unknown, errMsg);
                return rpcResponse;
            }


  
        }


        #region ProxyGeneration

        string namespaceDefinitionTemplate = @"Ext.namespace('{0}');
";
        string classDefinitionTemplate = @"

    {0} = Ext.extend(JsonRpc.Service, {{
            constructor : function()
            {{
                {0}.superclass.constructor.call(this, arguments);
            }}

           //Properties
           ,$url : '{1}'
{3}
           //Methods
{2}

}});
";
        string methodDefinitionTemplate =
@"           ,{0} : function({1})
            {{
                this.$call.call(this, {{{0}: this.createArgs(arguments)}});
            }}
";


        string methodListDefinitionTemplate = @"
           ,$methods : [
{0}
                       ]";
        string methodListParamsTemplate = @"{{name: '{0}', type: '{1}'}}, ";
        string methodListLineTemplate =
@"                {{ name: '{0}', params: [{1}]}},
";

        string GenerateScript()
        {
            string url = HttpContext.Current.Request.Url.ToString();
            string objectName = "", objectFullName = "";
            string objectNamespace = null;

            Type t = this.GetType();

            JsonRpcObject[] tInfos = (JsonRpcObject[])t.GetCustomAttributes(typeof(JsonRpcObject), false);
            if (tInfos.Length > 0)
            {
                JsonRpcObject tInfo = tInfos[0];
                objectName = tInfo.Name != null ? tInfo.Name : t.Name;
                objectNamespace = tInfo.Namespace != null ? tInfo.Namespace : t.Namespace;
            }
            else
            {
                objectName = t.Name;
            }
            objectFullName = objectName;
            if (objectNamespace != null) objectFullName = objectNamespace + "." + objectName;
            string methodDefinitions = "";


            MethodInfo[] methods = t.GetMethods();
            string methodsLines = "";

            string methodListLines = "";
            foreach (MethodInfo method in methods)
            {
                
                JsonRpcMethod[] mInfos = (JsonRpcMethod[])method.GetCustomAttributes(typeof(JsonRpcMethod), false);
                if (mInfos.Length > 0)
                {
                    //methodDefinitions += GenerateScript(method);



                    ParameterInfo[] paramInfos = method.GetParameters();
                    string functionParams = "";
                    string sfunctionParams = "";
                    string methodParams = "";

                    foreach (ParameterInfo pi in paramInfos)
                    {
                        functionParams += pi.Name + ", ";
                        
                        sfunctionParams += "'" + pi.Name + "', ";

                        methodParams += string.Format(methodListParamsTemplate, pi.Name, pi.ParameterType.FullName);

                    }

                    if (methodParams.Length > 0)
                        methodParams = methodParams.Remove(methodParams.LastIndexOf(", "));
                    if (sfunctionParams.Length > 0)
                        sfunctionParams = sfunctionParams.Remove(sfunctionParams.LastIndexOf(","));
                    if (functionParams.Length > 0)
                        functionParams = functionParams.Remove(functionParams.LastIndexOf(","));

                    string clientMethodName = mInfos[0].Name != null ? mInfos[0].Name : method.Name;


                    methodListLines += string.Format(methodListLineTemplate, clientMethodName, methodParams);
                    methodDefinitions += string.Format(methodDefinitionTemplate, clientMethodName, functionParams);
                    //methodsLines += string.Format(methodLineTemplate, clientMethodName, sfunctionParams);// +","; 

                }

            }
            if (methodListLines.Length > 0)
                methodListLines = methodListLines.Remove(methodListLines.LastIndexOf(","));
            if (methodsLines.Length > 0)
                methodsLines = methodsLines.Remove(methodsLines.LastIndexOf(","));
            //if (methodDefinitions.Length > 0)
            //    methodDefinitions = methodDefinitions.Remove(methodDefinitions.LastIndexOf(","));
            string methodListDefinition = "";
            methodListDefinition = string.Format(methodListDefinitionTemplate, methodListLines);
 
            methodsLines = string.Format(methodListDefinitionTemplate, methodsLines);
            string classDefinition = string.Format(classDefinitionTemplate, objectFullName, url, methodDefinitions, methodListDefinition);
            if (objectNamespace != null)
                classDefinition = string.Format(namespaceDefinitionTemplate, objectNamespace) + classDefinition;
            File.WriteAllText(HttpContext.Current.Request.PhysicalApplicationPath + "\\Generated.txt", classDefinition);

            return classDefinition;
        }

        #endregion

    }

}

